package com.gui;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.PushbackInputStream;
import java.lang.reflect.Method;
import java.math.BigInteger;
import java.net.InetAddress;
import java.net.Socket;
import java.net.URL;
import java.net.UnknownHostException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.Principal;
import java.security.PrivateKey;
import java.security.Security;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.text.DateFormat;
import java.text.spi.DateFormatProvider;
import java.util.Arrays;

import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JOptionPane;
import javax.swing.JPasswordField;
import javax.swing.text.DateFormatter;

import org.itr_rescue.dataGuard.encryption.asymmetric.RSACrypto;


public class PCLoginService implements Runnable{
	
	// 라이브러리 로딩
	
	static {
		try{
			System.loadLibrary("loadJni");
			
		}catch (UnsatisfiedLinkError e) {
			System.out.println("UnsatisfiedLinkError :: " + e.getMessage());
			MainWindows.setTextLog("UnsatisfiedLinkError :: " + e.getMessage());
		}
	}	// static
	
	 /** Load the library from the JAR file */
	final static String webURL = "http://127.0.0.1:10004/WebPage/";
	final static String serverIP = "127.0.0.1";
	final static int serverPort = 10002;
	final static int serverPort2 = 10003;
	final static int serverFilePort = 10005;
	final static int authmsgFilePort = 10006;
	
	InetAddress serverAddr;
	Socket socket;	
	Socket socket2;
	private ObjectOutputStream oos;
	private ObjectInputStream ois;
	
	
	// 로그인 회원가입 후 저장되는 사용자정보
	public static String userName;
	public static String userEmail;
	public static String userLocation;
	public static String userOrganization;
	public static String userCountry;
	public static String certSerialNumber;
	public static String certNotBefore;
	public static String certNotAfter;
	
	public int runType;
	
	public native static int readNFC();
	
	public PCLoginService(int type){
		try {
			serverAddr = InetAddress.getByName(serverIP);
			socket = new Socket(serverAddr, serverPort);
			//socket2 = new Socket(serverAddr, serverPort2);
			oos = new ObjectOutputStream(socket.getOutputStream());
			ois = new ObjectInputStream(socket.getInputStream());
		} catch (IOException e) {
			e.printStackTrace();
		}
		userName = "";
		userEmail = "";
		userLocation = "";
		userOrganization = "";
		userCountry = "";
		certSerialNumber = "";
		certNotBefore = "";
		certNotAfter = "";
		
		runType = type;
	}
	public void login(){
		
		try {
			//login request 메세지 전송
				//int nfcState = readNFC();
			while(readNFC() != 0){
				Thread.sleep(1000);
				System.out.println("1second sleep");
//				MainWindows.setTextLog("1second sleep");
			}
			oos.writeObject("LoginRequest");
			oos.flush();
			
			if(!PCverifyUser()){
				System.out.println("개인인증에러");
				MainWindows.setTextLog("개인인증에러");
				deleteUserInfo();
				return;
			//	return false;
			}else{
				String msg = (String)ois.readObject();
				if(msg.equals("LoginSuccess")){
					System.out.println("로그인 성공");
					MainWindows.setTextLog("로그인 성공");
					readCertInfo();
					loadBrowser(webURL);
					deleteUserInfo();
				}else if(msg.equals("NotRegisteredUserException")){
					deleteUserInfo();
					JOptionPane.showMessageDialog(null, "가입되지 않은 회원입니다. 회원등록 후 이용해 주세요","회원정보없음",JOptionPane.INFORMATION_MESSAGE);
				}
			}
			
		} catch (UnknownHostException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (InterruptedException e) {
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	//	return true;
	}

	
	private void deleteUserInfo() {
		String filename1 = "ClientCert.der";
		String filename2 = "PVKey";
		String filename3 = "temp.txt";
		File file1 = new File(filename1);
		File file2 = new File(filename2);
		File file3 = new File(filename3);
		
		file1.delete();
		file2.delete();
		file3.delete();
		
	}
	public boolean registerUser(){
		try {
			System.out.println("회원등록 요청중");
			MainWindows.setTextLog("회원등록 요청중");
			while(readNFC() != 0){
				Thread.sleep(1000);
				System.out.println("1second sleep");
				MainWindows.setTextLog("1second sleep");
			}
			oos.writeObject("RegisterUser");
			oos.flush();
			if(!PCverifyUser()){
				System.out.println("개인인증에러");
				MainWindows.setTextLog("개인인증에러");
				deleteUserInfo();
				return false;
			}
			String msg = (String)ois.readObject();
			if(msg.equals("RegisterUserSuccess")){
				System.out.println("회원등록 완료");
				MainWindows.setTextLog("회원등록 완료");
				deleteUserInfo();
				JOptionPane.showMessageDialog(null, "회원등록이 완료되었습니다.","등록 완료",JOptionPane.INFORMATION_MESSAGE);
			}else if(msg.equals("AlreadRegisteredException")){
				deleteUserInfo();
				JOptionPane.showMessageDialog(null, "이미 가입된 회원입니다. 로그인해주세요.","이미 등록됨",JOptionPane.INFORMATION_MESSAGE);
			}
			
		} catch (IOException e) {
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		return true;
	}
	
	public boolean logout(){
		if(socket != null){
			try {
				oos.writeObject("Logout");
				oos.flush();
				
			} catch (IOException e) {
				e.printStackTrace();
				return false;
			}
			
		}
		return true;
	}
	
	
	private void loadBrowser(String url) {
		String os = System.getProperty("os.name");
		Runtime runtime = Runtime.getRuntime();
		try{
			if(os.startsWith("Windows")){
				String cmd = "rundll32 url.dll, FileProtocolHandler " + url;
				Process p = runtime.exec(cmd);
			}
			else if(os.startsWith("Mac OS")){

				Class fileMgr = Class.forName("com.apple.eio.FileManager");
                Method openURL = fileMgr.getDeclaredMethod("openURL", new Class[] { String.class });
                openURL.invoke(null, new Object[] { url });
			}
		}catch(Exception ex){
			ex.printStackTrace();
		}
	}
	
	private boolean PCverifyUser(){
		//개인 인증서를 웹서버에 전송하고 
		//웹서버로부터 공개키로 암호화된 인증메세지를 전달받은 다음
		//인증메세지를 개인키로 복호화해서 사용자 인증을 한다.
		try {
			
			//개인 인증서 파일 전송;
			FileSendThread fst = new FileSendThread("ClientCert.der", serverFilePort);
			fst.run();
			//서버로부터 개인 인증서의 오류 여부 메시지 확인
			String msg = (String)ois.readObject();
			System.out.println(msg);
			MainWindows.setTextLog("웹서버로부터 온 메세지 :: " +msg);
			if(msg.equals("CertVerifySuccess")){
				// do nothing
				System.out.println("인증 확인 완료");
				MainWindows.setTextLog("인증 확인 완료");
			}else if(msg.equals("CertificateExpiredException")) {
				JOptionPane.showMessageDialog(null, "인증서 유효기간이 지났습니다(CertificateExpiredException)","Certificate Exception",JOptionPane.OK_OPTION);
				return false;
			}else if(msg.equals("CertificateNotYetValidException")) {
				JOptionPane.showMessageDialog(null, "인증서 유효기간이 잘못되었습니다(CertificateNotYetValidException)","Certificate Exception",JOptionPane.OK_OPTION);
				return false;
			}else if(msg.equals("InvalidKeyException")) {
				JOptionPane.showMessageDialog(null, "KEY가 잘못되었습니다 ","Certificate Exception",JOptionPane.OK_OPTION);
				return false;
			}else if(msg.equals("CertificateException")) {
				JOptionPane.showMessageDialog(null, "CertificateException","Certificate Exception",JOptionPane.OK_OPTION);
				return false;
			}else if(msg.equals("NoSuchAlgorithmException")) {
				JOptionPane.showMessageDialog(null, "인증서의 암호화 알고리즘을 처리할 수 없습니다 (NoSuchAlgorithmException)","Certificate Exception",JOptionPane.OK_OPTION);
				return false;
			}else if(msg.equals("NoSuchProviderException")) {
				JOptionPane.showMessageDialog(null, "인증서의  공급자를 확인할 수 없습니다 (NoSuchProviderException)","Certificate Exception",JOptionPane.OK_OPTION);
				return false;
			}else if(msg.equals("SignatureException")) {
				JOptionPane.showMessageDialog(null, "인증서의 서명이 잘못되었습니다 (SignatureException)","Certificate Exception",JOptionPane.OK_OPTION);
				return false;
			}
			
			//인증메세지 파일 수신
			InputStreamReader isr = new InputStreamReader(socket.getInputStream());
			BufferedReader reader = new BufferedReader(isr);
			String str;
			while((str = reader.readLine())== null){
				System.out.println("waintg...");
				MainWindows.setTextLog("waintg...");
			}
			System.out.println(str);
			MainWindows.setTextLog("웹서버로부터 온 메세지 :: "+str);
			System.out.println("인증메세지 수신중");
			MainWindows.setTextLog("인증메세지 수신중");
			FileReceiveThread frt = new FileReceiveThread(serverIP, authmsgFilePort, "temp.txt");
			frt.run();
			System.out.println("인증메세지 수신완료");
			MainWindows.setTextLog("인증메세지 수신완료");
			//인증메세지 복호화
			/******************** 암호화된 개인키 정보를 읽어들여 비밀번호를 통해 복호화 ********************/
			
			char[] depwd = getPassword();
			ComfirmPassword comfirm = new ComfirmPassword();
			boolean comf = comfirm.ComfirmPWD(depwd);
			if (!comf) {
				System.out.println("비밀번호가 일치하지 않습니다.");
				MainWindows.setTextLog("비밀번호 불일치");
				Icon icon = new ImageIcon("Lock-icon.png");
			    JOptionPane.showMessageDialog(null, "비밀번호가 일치하지 않습니다.","비밀번호 에러",JOptionPane.OK_OPTION,icon);
				// 비밀번호가 일치하지 않으므로 web에 인증실패 메세지를 보낸다
				oos.writeObject("-1");
				oos.flush();

				return false;
			} else {
				System.out.println("비밀번호가 일치합니다.");
				MainWindows.setTextLog("비밀번호 일치");
				Icon icon = new ImageIcon("Unlock-icon.png");
			    //JOptionPane.showMessageDialog(null, "비밀번호가 일치합니다.","비밀번호 확인",JOptionPane.OK_OPTION,icon);
				// 비밀번호가 일치하므로 웹에서 온 암호화된 인증메세지를 복호화한다.
				/****** 복호화된 개인키를 이용하여 인증메세지 복호화 ********/
				
				byte[] decryptPVkey = comfirm.getDecodedPVKey();
				Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
				KeyFactory kf = KeyFactory.getInstance("RSA", "BC");
				PKCS8EncodedKeySpec ks = new PKCS8EncodedKeySpec(decryptPVkey);
				PrivateKey priKey = kf.generatePrivate(ks);
				
				byte[] encryptMsg = null;
				FileInputStream fis = new FileInputStream("temp.txt");
				encryptMsg = new byte[fis.available()];
				while(fis.read(encryptMsg) != -1){;}
				fis.close();
				System.out.println("암호화된 인증메세지 : "+ new String(encryptMsg));
				MainWindows.setTextLog("암호화된 인증메세지 :: "+ new String(encryptMsg));
				byte[] decryptMsg = DecryptMsg(priKey, encryptMsg);
				String deMsgStr = new String(decryptMsg);
				System.out.println("복호화된 인증메세지 : "+ deMsgStr);
				MainWindows.setTextLog("복호화된 인증메세지 :: "+ deMsgStr);
			//인증메세지 재전송
				oos.writeObject(deMsgStr);
				oos.flush();
				return true;
			}
		} catch (UnknownHostException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		} catch (NoSuchProviderException e) {
			e.printStackTrace();
		} catch (InvalidKeySpecException e) {
			e.printStackTrace();
		} catch (InvalidKeyException e) {
			e.printStackTrace();
		} catch (IllegalBlockSizeException e) {
			e.printStackTrace();
		} catch (NoSuchPaddingException e) {
			e.printStackTrace();
		} catch (InvalidAlgorithmParameterException e) {
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
		return false;
	}
	
	public void readCertInfo(){
		X509Certificate clientCert = null;
		try {
			FileInputStream fis;
			fis = new FileInputStream("ClientCert.der");
			CertificateFactory certificateFactory = CertificateFactory
			.getInstance("X509");
			clientCert = (X509Certificate) certificateFactory
			.generateCertificate(fis);
			fis.close();
			Principal userInfo = clientCert.getSubjectDN();
			userName = userInfo.getName().split(",")[1].split("=")[1];
			userEmail = userInfo.getName().split(",")[0].split("=")[1];;
			userLocation = userInfo.getName().split(",")[2].split("=")[1];
			userOrganization = userInfo.getName().split(",")[3].split("=")[1];
			userCountry = userInfo.getName().split(",")[4].split("=")[1];
			certSerialNumber = clientCert.getSerialNumber().toString();
			certNotBefore = clientCert.getNotBefore().toString();
			DateFormat df = DateFormat.getDateInstance();
			certNotBefore = df.format(clientCert.getNotBefore());
			certNotAfter = df.format(clientCert.getNotAfter());
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (CertificateException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		
		
	}
	
	private char[] readPasswd(InputStream in) throws IOException {
		char[] lineBuffer;
		char[] buf;

		buf = lineBuffer = new char[128];

		int room = buf.length;
		int offset = 0;
		int c;

		loop: while (true) {
			switch (c = in.read()) {
			case -1:
			case '\n':
				break loop;

			case '\r':
				int c2 = in.read();
				if ((c2 != '\n') && (c2 != -1)) {
					if (!(in instanceof PushbackInputStream)) {
						in = new PushbackInputStream(in);
					}
					((PushbackInputStream) in).unread(c2);
				} else
					break loop;

			default:
				if (--room < 0) {
					buf = new char[offset + 128];
					room = buf.length - offset - 1;
					System.arraycopy(lineBuffer, 0, buf, 0, offset);
					Arrays.fill(lineBuffer, ' ');
					lineBuffer = buf;
				}
				buf[offset++] = (char) c;
				break;
			}
		}

		if (offset == 0) {
			return null;
		}

		char[] ret = new char[offset];
		System.arraycopy(buf, 0, ret, 0, offset);
		Arrays.fill(buf, ' ');

		return ret;
	}
	
	/** 개인키를 사용하여 메세지 암호화 **/
	private byte[] DecryptMsg(PrivateKey priKey, byte[] encryptMsg) {
		byte[] encrypttext = encryptMsg;
		RSACrypto enigma = new RSACrypto();
		byte[] recorverytext = null;
		try {
			recorverytext = enigma.decrypt(encrypttext, priKey);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return recorverytext;
	}
	
	private char[] getPassword() {
		JPasswordField pwd = new JPasswordField(10);
		Icon icon = new ImageIcon("Lock-icon.png");
		int action = JOptionPane.showConfirmDialog(null, pwd, "Enter Password",
				JOptionPane.YES_OPTION, JOptionPane.PLAIN_MESSAGE, icon);
		if (action == JOptionPane.CLOSED_OPTION
				|| action == JOptionPane.NO_OPTION) {
			System.out.println("close");
			MainWindows.setTextLog("close");
			return null;
		} else {
			return pwd.getPassword();
		}
	}

	@Override
	public void run() {
			switch(runType){
			case 0:
				login();
				break;
			case 1:
				registerUser();
				break;
			case 2:
				logout();
				break;
			}
				
	}
	
}
